package net.chowda.castcluster.provider;

import be.roam.drest.service.youtube.YouTubeService;
import be.roam.drest.service.youtube.YouTubeVideo;
import be.roam.drest.service.youtube.YouTubeException;
import net.chowda.castcluster.CastVideoItem;
import net.chowda.castcluster.Video;
import net.chowda.castcluster.VideoProvider;
import net.chowda.castcluster.VideoSource;
import net.chowda.castcluster.util.CorruptFeedException;
import net.chowda.castcluster.util.RSSFeedAggregator;
import net.chowda.castcluster.util.*;
import org.apache.commons.httpclient.HttpClient;
import org.apache.commons.httpclient.methods.GetMethod;
import org.apache.log4j.Logger;
import org.xml.sax.SAXException;

import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Date;

/**
 * this class implements the VideoProvider abstract class to provide
 * access to youtube video resources.
 *
 * User: dbachelder
 *
 *
 */
public class YouTubeProvider extends VideoProvider {
    private static final Logger LOG = LogUtil.getLogger(YouTubeProvider.class);

    private static final String YOUTUBE_DEV_ID = "youtubeDevId";
    private static final String CONVERT_EXTENSION = ".mp4";

    public YouTubeProvider() {
    }

    /**
     * @return the path of the converted video...
     *          (or at least where it should be... if the video has not been fetched to will not reside at this location)
     */
    public String getVideoPath(YouTubeVideo vid) {
        return getVideoPath(vid.getId());
    }

    /**
     * @return the path of the converted video...
     *          (or at least where it should be... if the video has not been fetched to will not reside at this location)
     */
    public String getVideoPath(CastVideoItem vid) {
        String id = vid.getId();
        return getVideoPath(id);
    }

    public String getVideoPath(String videoId) {
        return CastClusterConfig.getInstance().getGlobalProp(CastClusterConfig.CONFIG_BASE_DIR)
                                                + File.separator + "video" + File.separator + videoId + CONVERT_EXTENSION;
    }

    public String getProviderFeed(String url) {
        try {
            return RSSFeedAggregator.createPodcastFromVideos("youtube", "youtube", getVideos(url));
        } catch (CorruptFeedException e) {
            LOG.error("this shouldn't happen!", e);
            throw new RuntimeException(e);
        }
    }

    /**
     * create generic VideoItem from youtube request
     */
    public List<Video> getProviderVideos(String url) {
        List<Video> videos = new ArrayList<Video>(20);
        String[] parts = url.split("\\|");
        LOG.info("getting youtube details for url: " + url);
        YouTubeService service = new YouTubeService(getDevId());
        try {
            if("user".equals(parts[1])) {
                List<YouTubeVideo> userVids = service.getVideosByUser(parts[2], 1, 100);
                if (userVids != null) {
                    for (YouTubeVideo vid : userVids) {
                        videos.add(drestVidToVidItem(vid, "yt|video|"+vid.getId()));
                    }
                }
            } else if("video".equals(parts[1])) {
                try {
                    YouTubeVideo details = service.getVideoDetails(parts[2]);
                    if (details != null) {
                        videos.add(drestVidToVidItem(details, "yt|video|"+parts[2]));
                    }
                } catch (YouTubeException e) {
                    // this exception means there was an error response from youtube.
                    // set everything in the video to bogus items.
                    // if the video has been pulled for some reason, we'll end up here.
                    // we also might get here if the video ID was never valid in the first place
                    // the message from youtube should be enough info to tell the user what's up
                    CastVideoItem item = new CastVideoItem();
                    // description will be the error message text.
                    item.setDescription(e.getMessage());
                    // this is the only other value we really care about
                    item.setAvailable(false);
                    // we'll set a new ID... with a prefix.. this should ensure that consumers don't overwrite previous
                    // good information... in case they got lucky and got the video before it was pulled.
                    item.setId("bad_" + parts[2]);

                    item.setUrl("");
                    item.setTitle(url);
                    item.setAuthor("unknown");
                    item.setTimeUploaded(new Date());
                    item.setLengthInSeconds(1);
                    item.setThumbnailUrl("");
                    item.setVidSize(1);
                    item.setVidType("text/plain");
                    videos.add(item);
                }
            } else if("favs".equals(parts[1])) {
                List<YouTubeVideo> list = service.getFavoriteVideos(parts[2]);
                if (list != null) {
                    for (YouTubeVideo vid : list) {
                        videos.add(drestVidToVidItem(vid, "yt|video|"+vid.getId()));
                    }
                } else {
                    LOG.warn("you requested the favorite videos for user: " + parts[2] + " but I did not find any.");
                }
            }
        } catch (IOException e) {
            LOG.error("could not fetch youtube url!", e);
            throw new RuntimeException(e);
        } catch (SAXException e) {
            LOG.error("could not fetch youtube url!", e);
            throw new RuntimeException(e);
        }
        return videos;
    }

    /**
     * fetch youtube flash and convert to quicktime.
     */
    public String fetchProviderVideo(String videoUrl) {
        String videoId = videoUrl.split("\\|")[2];

        // make sure video dir exists.
        String destDir = CastClusterConfig.getInstance().getGlobalProp(CastClusterConfig.CONFIG_BASE_DIR) + File.separator + "video";
        new File(destDir).mkdirs();

        String sourceFile = destDir + File.separator + videoId + ".flv";
        String destFile = getVideoPath(videoId);

        if (!new File(destFile).exists()) {
            GetMethod method = new GetMethod("http://www.youtube.com/v/" + videoId);
            HttpClient client = new HttpClient();
            try {
                client.executeMethod(method);
                String uri = method.getURI().toString();
                uri = uri.replace("swf/l.swf", "get_video");
                method = new GetMethod(uri);
                LOG.info("downloading youtube video: " + videoId);
                int s = client.executeMethod(method);
            } catch (IOException e) {
                LOG.error("could not fetch youtube video!", e);
                throw new RuntimeException(e);
            }
            try {
                FileOutputStream fileOutputStream = new FileOutputStream(sourceFile);
                BufferedInputStream is = new BufferedInputStream(method.getResponseBodyAsStream());

                byte[] b = new byte[2024];
                int count;
                while((count = is.read(b)) > -1) {
                    fileOutputStream.write(b, 0, count);
                }
                fileOutputStream.flush();
                fileOutputStream.close();
            } catch (IOException e) {
                LOG.error("could not write youtube video to disk!", e);
                throw new RuntimeException(e);
            }

            MovieUtil.convertVideoToMPEG4(sourceFile, destFile);
        } else {
            LOG.info("video " + videoId + " already exists... no need to fetch or convert, yay!");
        }
        return destFile;
    }

    /**
     * delete flv file and converted video.. should be called after a successful fetch.
     *
     * @param ccUrl - this is the cast cluster url to clean up, should only be "video" items.
     */
    public void providerCleanupAfterFetch(String ccUrl) {
        String videoId = ccUrl.split("\\|")[2];
        LOG.info("cleanup up fetched file and converted file for: " + ccUrl);

        String destDir = CastClusterConfig.getInstance().getGlobalProp(CastClusterConfig.CONFIG_BASE_DIR) + File.separator + "video";
        String source = destDir + File.separator + videoId + ".flv";
        String dest = getVideoPath(videoId);

        File sourceFile = new File(source);
        if(sourceFile.exists()) sourceFile.delete();
        File destFile = new File(dest);
        if(destFile.exists()) destFile.delete();
    }


    public VideoSource getProviderSourceForCCUrl(String url) {
        VideoSource source = new VideoSource();
        source.setProvider(this);
        source.setCcUrl(url);
        source.setVideos(getVideos(url));

        // set the url bits according to what we parsed out.
        String foundItem = url.substring(url.lastIndexOf('|')+1);
        if(url.startsWith("yt|user")) {
            source.setUrl("http://www.youtube.com/profile?user=" + foundItem);
            source.setName("Youtube user: " + foundItem);
        } else if(url.startsWith("yt|favs")) {
            source.setUrl("http://www.youtube.com/profile_favorites?user=" + foundItem);
            source.setName("Youtube user favs: " + foundItem);
        } else if(url.startsWith("yt|video")) {
            source.setUrl("http://www.youtube.com/watch?v=" + foundItem);
            source.setName("Youtube vid - " + source.getVideos().get(0).getTitle());
        } else {
            return null;
        }

        return source;
    }

    // WORK IN PROGRESS.... this whole idea... working on it.
    /**
     * right now this work is all done in the LinkAction... but it should be here.
     */
    public List<VideoSource> getProviderSourcesForWebUrl(String url) {
        List<VideoSource> sources = new ArrayList<VideoSource>();

        VideoSource source = new VideoSource();
        source.setProvider(this);
        source.setUrl(url);

        // user pages usually look like this: http://www.youtube.com/profile?user=username
        String user     = getFirstMatchingGroup("profile\\?.*?user=([^&]+)", url);
        // favorites usually look like this: http://www.youtube.com/profile_favorites?user=username
        String userFavs = getFirstMatchingGroup("profile_favorites.*?user=([^&]+)", url);
        // videos look like this: http://www.youtube.com/watch?v=VIDEOID
        String video    = getFirstMatchingGroup("watch.*?v=([^&]+)", url);
        if(user == null) {
            // sometimes the user url looks like this: http://www.youtube.com/user/username
            user = getFirstMatchingGroup("\\/user\\/([^&^\\/]+)", url);
        }

        // set the url bits according to what we parsed out.
        String foundItemType;
        String foundItem;
        if(user != null) {
            foundItemType = "user";
            foundItem = user;
        } else if(userFavs != null) {
            foundItemType = "favs";
            foundItem = userFavs;
        } else if(video != null) {
            foundItemType = "video";
            foundItem = video;
        } else {
            return null;
        }

        source.setCcUrl("yt|" + foundItemType + "|" + foundItem);
        source.setVideos(getVideos(source.getCcUrl()));
        if(user != null) {
            source.setName("Youtube user - " + foundItem);
        } else if(userFavs != null) {
            source.setName("Youtube user favs - " + foundItem);
        } else {
            // it's a video, let's grab the actual title.
            source.setName("Youtube vid - " + source.getVideos().get(0).getTitle());
        }

        sources.add(source);
        return sources;
    }

    /**
     * this returns the youtube dev id for castcluster if one is not set in the config.
     */
    private String getDevId() {
        String devId = getProperties().get(YOUTUBE_DEV_ID);
        if(devId == null || devId.trim().length() == 0) devId = "AQ-VImmaX-w";
        return devId;
    }

    /**
     * convert drest youtubevideo object into generic castcluster video item.
     */
    private CastVideoItem drestVidToVidItem(YouTubeVideo vid, String vidUrl) {
        CastVideoItem item = new CastVideoItem();
        item.setAuthor(vid.getAuthor());
        item.setDescription(vid.getDescription());
        item.setId(vid.getId());
        item.setLengthInSeconds(vid.getLengthInSeconds());
        item.setThumbnailUrl(vid.getThumbnailUrl());
        item.setTimeUploaded(vid.getTimeUploaded());
        item.setTitle(vid.getTitle());
        item.setAvailable(vid.isEmbedable());
        item.setVidType("video/mp4");
        item.setUrl(CastClusterConfig.getInstance().getGlobalProp(CastClusterConfig.BASE_URL) + "/PlayList.mp4?videoUrl=" + Base64Util.encode(vidUrl));
        return item;
    }
}
